/*
 * BaseMessageFilter.java    0.0.1    08/dic/2009
 * 
 * Copyright (c) 2009 mentalsmash.org
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * The use of the Apache License does not indicate that this project is
 * affiliated with the Apache Software Foundation.
 */
package org.mentalsmash.tazio.transparency.impl;

import java.util.ArrayList;
import java.util.List;

import org.mentalsmash.tazio.transparency.commons.messages.EndOfProtocolMessage;
import org.mentalsmash.tazio.transparency.commons.messages.TazioMessage;
import org.mentalsmash.tazio.transparency.commons.messages.TazioMessageID;
import org.mentalsmash.tazio.transparency.commons.messages.Unknown_Answer_Message;
import org.mentalsmash.tazio.transparency.interfaces.Dispatcher;
import org.mentalsmash.tazio.transparency.interfaces.MessageFilter;
import org.mentalsmash.tazio.transparency.interfaces.TransparencyMonitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.name.Named;

/**
 * This class is the base implementation for the {@link MessageFilter}
 * interface.
 * 
 * @version 0.0.1 08/dic/2009
 * @author Enrico Savioli <savioli.enrico@gmail.com>
 */
@Singleton
public class BaseMessageFilter implements MessageFilter {

    private final static Logger log = LoggerFactory
	    .getLogger(BaseMessageFilter.class);

    private static MessageFilter messageFilter;

    private final int MAX_ANSWERS_NUMBER;

    private final TransparencyMonitor tester;
    private final TazioMessage UNKNOWN_RESPONSE;
    private final Dispatcher dispatcher;
    private final List<Answer> answers;

    // private final HashMap<TazioMessageID, TazioMessage> map;

    @Inject
    public BaseMessageFilter(Dispatcher dispatcher, TransparencyMonitor tester,
	    @Named("max_answers_number") int MAX_ANSWERS_NUMBER) {

	BaseMessageFilter.messageFilter = this;

	this.MAX_ANSWERS_NUMBER = MAX_ANSWERS_NUMBER;
	this.dispatcher = dispatcher;
	this.tester = tester;
	this.answers = new ArrayList<Answer>();
	// this.map = new HashMap<TazioMessageID, TazioMessage>();
	this.UNKNOWN_RESPONSE = new Unknown_Answer_Message();

    }

    /*
     * (non-Javadoc)
     * 
     * @seeorg.mentalsmash.tazio.transparency.MessageFilter#handleMessage(org.
     * mentalsmash.tazio.transparencyTemp.commons.messages.TazioMessage)
     */
    /**
     * This implementation exploits an HashMap which uses {@link TazioMessageID}
     * s as keys and {@link TazioMessage}s as values.
     * <p>
     * When the <code>handleMessage()</code> method is received it checks if an
     * entry for the corresponding {@link TazioMessageID} is already contained.
     * If it is contained it checks it value, which could be equal to
     * <code>UNKNOWN_RESPONSE</code>, meaning that the system is still computing
     * the answer, or unequal, this would mean that the answer has been computed
     * and that this is his value, which will be sent back to the sender of the
     * original message.
     * </p>
     * <p>
     * In case the {@link TazioMessageID} value is not contained in the map, an
     * entry will be added to the map, with value <code>UNKNOWN_RESPONSE</code>
     * and the answer will be passed to the dispatcher, which will will return
     * the computed answer, subsequently added to the map, thus substituting
     * <code>UNKNOWN_RESPOSNSE</code> and sent back to the protocol handler.
     * </p>
     */
    @Override
    public TazioMessage handleMessage(TazioMessage message) {

	tester.MessageFilter_handleMessage();

	TazioMessageID key = message.getMsgID();
	TazioMessage answer = null;
	Answer answerObj = null;
	Answer toReturn = null;
	boolean found = false;
	int index = -1;

	// synchronized (map) {
	//			
	// if (map.containsKey(key)) {
	// if (!(map.get(key) instanceof Unknown_Answer_Message)) {
	// return map.get(key);
	// } else {
	// return null;
	// }
	// }else{
	// map.put(key, UNKNOWN_RESPONSE);
	// }
	// }

	// Checks if there is an answer in the list whose TazioMessageID is
	// equal to the
	// one of the message we are looking for
	synchronized (answers) {

	    // checks if it contains the message with that TazioNodeID
	    for (index = 0; index < answers.size(); index++) {

		toReturn = answers.get(index);
		if (toReturn.getID().equals(message.getMsgID())) {

		    log.debug("Found the desired message");
		    found = true;
		    break;

		}

	    }

	    if (found) {

		log.debug("Found an entry for the message " + key);

		// int index = answers.indexOf(key);
		if (!(answers.get(index).getMessage() instanceof Unknown_Answer_Message)) {

		    log
			    .debug("It is not an Unknown_Answer_Message. Returning it.");
		    return answers.get(index).getMessage();

		} else {

		    log
			    .debug("It is an Unknown_Answer_Message. Returning null.");
		    return null;

		}
	    } else {

		log.debug("Did not find an entry for the message " + key
			+ ". Adding a new entry.");
		// if the list contains too many elements deletes the oldest one
		// whose
		// answer is not being computed.
		if (answers.size() == this.MAX_ANSWERS_NUMBER) {

		    log
			    .debug("Answers limit reached. Deleting the oldest entry.");
		    TazioMessage toDelete;

		    // searches the list for the oldest message whose answer has
		    // been
		    // computed.
		    for (int i = 0; i < answers.size(); i++) {

			toDelete = answers.get(i).getMessage();
			if (toDelete instanceof Unknown_Answer_Message) {

			    continue;

			}

			// removes it and exits from the loop.
			log.debug("Entry deleted");
			answers.remove(i);
			break;

		    }

		}

		// adds the reference to the new answer to the list.
		answerObj = new Answer(key, UNKNOWN_RESPONSE);
		answers.add(answerObj);
		log
			.debug("Added the new Answer object to the list of answers.");
		// map.put(key, UNKNOWN_RESPONSE);
	    }
	}

	log.debug("Retrieving the answer from the dispatcher.");
	answer = dispatcher.handleMessage(message);
	log.debug("Answer retrieved. Its value is: " + answer);
	// synchronized (map) {
	//		
	// map.put(key, answer);
	//			
	// }

	synchronized (answers) {

	    tester.MessageFilter_add();

	    // creating an EndOfProtocolMessage if the computed answer is null.
	    if (answer.getPayload() == null) {
		answer = new EndOfProtocolMessage();
	    }

	    log.debug("Updating the list with the new answer.");
	    // int index = answers.indexOf(answerObj);
	    index = answers.indexOf(answerObj);
	    answers.remove(index);
	    answers.add(new Answer(key, answer));
	    log.debug("Updated the entry for the answer to " + key + " "
		    + "with the message: " + answer);

	}

	return answer;

    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mentalsmash.tazio.transparency.MessageFilter#add(org.mentalsmash.
     * tazio.transparencyTemp.commons.messages.TazioMessage)
     */
    @Override
    public void add(TazioMessage message) {

	// synchronized (map) {
	//			
	// TazioMessageID messageID = message.getMsgID();
	//			
	// map.put(messageID, message);
	//			
	// }

	tester.MessageFilter_add();

	synchronized (answers) {

	    Answer alreadyContained = null;

	    for (int i = 0; i < answers.size(); i++) {

		alreadyContained = answers.get(i);
		if (alreadyContained.getID().equals(message.getMsgID())) {

		    answers.remove(i);
		    log.debug("Answer already contained, removing it.");
		    break;

		}

	    }

	    log.debug("Adding an answer for the message " + message.getMsgID()
		    + " with value: " + message);
	    answers.add(new Answer(message.getMsgID(), message));

	}

    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mentalsmash.tazio.transparency.MessageFilter#remove(org.mentalsmash
     * .tazio.transparencyTemp.commons.messages.TazioMessage)
     */
    @Override
    public void remove(TazioMessage message) {

	// synchronized (map) {
	//			
	// TazioMessageID messageID = message.getMsgID();
	//			
	// map.remove(messageID);
	//				
	// }

	tester.MessageFilter_remove();

	synchronized (answers) {

	    Answer toDelete;

	    log
		    .debug("Adding the answer for the message "
			    + message.getMsgID());
	    for (int i = 0; i < answers.size(); i++) {

		toDelete = answers.get(i);
		if (toDelete.getMessage().equals(message)) {

		    answers.remove(i);
		    log.debug("Answer removed.");
		    break;

		}

	    }

	}
    }

    /**
     * This private class is used to associate a {@link TazioMessageID} to its
     * {@link TazioMessage answer} in order to store them together in an entry
     * of a {@link List}.
     * 
     * @version 0.0.1 23/dic/2009
     * @author Enrico Savioli <savioli.enrico@gmail.com>
     */
    private class Answer {

	private final TazioMessageID id;
	private final TazioMessage message;

	public Answer(TazioMessageID id, TazioMessage message) {

	    this.id = id;
	    this.message = message;

	}

	public TazioMessageID getID() {

	    return this.id;

	}

	public TazioMessage getMessage() {

	    return this.message;

	}

    }

    @Override
    public int size() {

	return answers.size();

    }

    /**
     * This method is used in order to make {@link MessageFilter} accessible as
     * an old style Singleton, not as an injected Singleton as with Guice.
     * <p>
     * The reason for this is that the underlying {@link ProtocolHandler} needs
     * it, but for problems with the reflection it has to be created using the
     * default constructor, and with that condition comes the problem that the
     * {@link MessageFilter} would not be injected, thus it will have to be
     * retrieved when needed as an old school Singleton.
     * </p>
     * 
     * @return the Instance of the {@link MessageFilter} currently used in the
     *         system.
     */
    public static MessageFilter getInstanceOldStyle() {

	return messageFilter;

    }

}
